/* machine_spi.c */

#include <stdio.h>

#include "machine_spi.h"
#include "hal_gpio.h"
#include "hal_rcc.h"
#include "hal_spi.h"

#include "clock_init.h"

#include "extmod/machine_spi.h"

const machine_hw_spi_obj_t *hw_spi_find(mp_obj_t user_obj);
void machine_hw_spi_enable_clock(uint32_t hw_spi_id, bool enable);
uint8_t hw_spi_xfer(machine_hw_spi_obj_t *self, uint8_t tx);

STATIC void machine_hw_spi_transfer(mp_obj_base_t *self_in, size_t len, const uint8_t *src, uint8_t *dest)
{
    machine_hw_spi_obj_t *self = (machine_hw_spi_obj_t *)self_in;

    if (dest == NULL) /* only write. */
    {
        for (size_t i = 0; i < len; i++)
        {
            hw_spi_xfer(self, src[i]);
        }
    }
    else if (src == NULL) /* only read. */
    {
        for (size_t i = 0; i < len; i++)
        {
            dest[i] = hw_spi_xfer(self, 0xff);
        }
    }
    else // we need to read and write data
    {
        for (size_t i = 0; i < len; i++)
        {
            dest[i] = hw_spi_xfer(self, src[i]);
        }
    }
}

/******************************************************************************/
// MicroPython bindings for hw_spi.

STATIC void machine_hw_spi_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind)
{
    machine_hw_spi_obj_t *self = MP_OBJ_TO_PTR(self_in);
    mp_printf(print, "SPI(id=%u, baudrate=%u, polarity=%u, phase=%u), on MOSI(%s), MISO(%s), SCK(%s)",
             self->spi_id,
             self->conf->baudrate,
             self->conf->polarity,
             self->conf->phase,
             qstr_str(self->mosi_pin_obj->name),
             qstr_str(self->miso_pin_obj->name),
             qstr_str(self->sck_pin_obj->name)
             );
}

STATIC void machine_hw_spi_init(mp_obj_base_t *self_in, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
{
    machine_hw_spi_obj_t *self = (machine_hw_spi_obj_t *)self_in;

    enum
    {
        ARG_baudrate,
        ARG_polarity,
        ARG_phase,
        ARG_firstbit,
    };
    static const mp_arg_t allowed_args[] =
    {
        { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = -1} },
        { MP_QSTR_polarity, MP_ARG_INT, {.u_int = -1} },
        { MP_QSTR_phase,    MP_ARG_INT, {.u_int = -1} },
        { MP_QSTR_firstbit, MP_ARG_INT, {.u_int = 0 } },
    };
    mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
    mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

    if (args[ARG_baudrate].u_int != -1)
    {
        self->conf->baudrate = args[ARG_baudrate].u_int;
    }
    if (args[ARG_polarity].u_int != -1)
    {
        self->conf->polarity = args[ARG_polarity].u_int;
    }
    if (args[ARG_phase].u_int != -1)
    {
        self->conf->phase = args[ARG_phase].u_int;
    }

    /* clock. */
    machine_hw_spi_enable_clock(self->spi_id, true);


    /* pinmux. */
    GPIO_Init_Type gpio_init;
    gpio_init.Speed = GPIO_Speed_50MHz;
    /* sck pin. */
    gpio_init.Pins = ( 1u << (self->sck_pin_obj->gpio_pin) );
    gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
    GPIO_Init(self->sck_pin_obj->gpio_port, &gpio_init);
    GPIO_PinAFConf(self->sck_pin_obj->gpio_port, gpio_init.Pins, self->sck_pin_af);
    /* mosi pin. */
    gpio_init.Pins = ( 1u << (self->mosi_pin_obj->gpio_pin) );
    gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
    GPIO_Init(self->mosi_pin_obj->gpio_port, &gpio_init);
    GPIO_PinAFConf(self->mosi_pin_obj->gpio_port, gpio_init.Pins, self->mosi_pin_af);
    /* miso pin. */
    gpio_init.Pins = ( 1u << (self->miso_pin_obj->gpio_pin) );
    gpio_init.PinMode = GPIO_PinMode_In_PullUp;
    GPIO_Init(self->miso_pin_obj->gpio_port, &gpio_init);
    GPIO_PinAFConf(self->miso_pin_obj->gpio_port, gpio_init.Pins, self->miso_pin_af);


    /* spi engine. */
    SPI_Master_Init_Type spi_init;
    spi_init.ClockFreqHz = CLOCK_APB2_FREQ;
    spi_init.BaudRate = self->conf->baudrate;
    spi_init.PolPha = (self->conf->polarity << 1) | self->conf->phase;
    spi_init.DataWidth = 8u;
    spi_init.XferMode = SPI_XferMode_TxRx;
    spi_init.AutoCS = false;
    spi_init.LSB = (args[ARG_baudrate].u_int == 1u); /* MICROPY_PY_MACHINE_SPI_LSB */
    SPI_InitMaster(self->spi_port, &spi_init);
    SPI_Enable(self->spi_port, true);
}

mp_obj_t machine_hw_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args)
{
    MP_MACHINE_SPI_CHECK_FOR_LEGACY_SOFTSPI_CONSTRUCTION(n_args, n_kw, args);

    const machine_hw_spi_obj_t *spi_obj = hw_spi_find(args[0]);

    // set defaults
    spi_obj->conf->baudrate = 80000000L; /* 8Mhz. */
    spi_obj->conf->polarity = 0;
    spi_obj->conf->phase = 0;
    mp_map_t kw_args;
    mp_map_init_fixed_table(&kw_args, n_kw, args + n_args);
    machine_hw_spi_init((mp_obj_base_t *)spi_obj, n_args - 1, args + 1, &kw_args);

    return MP_OBJ_FROM_PTR(spi_obj);
}

STATIC const mp_machine_spi_p_t machine_hw_spi_p =
{
    .init = machine_hw_spi_init,
    .transfer = machine_hw_spi_transfer,
};

const mp_obj_type_t machine_hw_spi_type =
{
    { &mp_type_type },
    .name = MP_QSTR_HW_SPI,
    .print = machine_hw_spi_print,
    .make_new = machine_hw_spi_make_new,
    .protocol = &machine_hw_spi_p,
    .locals_dict = (mp_obj_dict_t *)&mp_machine_spi_locals_dict,
};


/* 格式化hw_spi对象，传入参数无论是已经初始化好的hw_spi对象，还是一个表示hw_spi清单中的索引编号，通过本函数都返回一个期望的hw_spi对象。 */
const machine_hw_spi_obj_t *hw_spi_find(mp_obj_t user_obj)
{
    /* 如果传入参数本身就是一个uart的实例，则直接送出这个UART。 */
    if ( mp_obj_is_type(user_obj, &machine_hw_spi_type) )
    {
        return user_obj;
    }

    /* 如果传入参数是一个uart通道号，则通过索引在UART清单中找到这个通道，然后送出这个通道。 */
    if ( mp_obj_is_small_int(user_obj) )
    {
        uint8_t idx = MP_OBJ_SMALL_INT_VALUE(user_obj);
        if ( idx < MACHINE_HW_SPI_NUM )
        {
            return machine_hw_spi_objs[idx];
        }
    }

    mp_raise_ValueError(MP_ERROR_TEXT("HW SPI doesn't exist"));
}

void machine_hw_spi_enable_clock(uint32_t hw_spi_id, bool enable)
{
    switch (hw_spi_id)
    {
        case 0u:
            RCC_EnableAPB2Periphs(RCC_APB2_PERIPH_SPI1, enable);
            break;
        case 1u:
            RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_SPI2, enable);
            break;
        case 2u:
            RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_SPI3, enable);
            break;
        default:
            break;
    }
}

uint8_t hw_spi_xfer(machine_hw_spi_obj_t *self, uint8_t tx)
{
    while ( SPI_STATUS_TX_FULL & SPI_GetStatus(self->spi_port) )
    {}
    SPI_PutData(self->spi_port, tx);

    while (0u == (SPI_STATUS_RX_DONE & SPI_GetStatus(self->spi_port)) )
    {}
    return SPI_GetData(self->spi_port);
}

/* EOF. */

